package com.szwistar.common.comm;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.core.session.IoSessionConfig;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class StreamFrameAnalyzer {
	protected static Logger LOG = LoggerFactory.getLogger(StreamFrameAnalyzer.class);
	protected static Logger LOGCOMM = LoggerFactory.getLogger("comm");

	// 同步标志
	public enum SyncState {
		MISSSYNC,		///< 未同步(初始状态)
		PENDING,		///< 正在进行同步
		SYNCHRONIZED,	///< 同步已建立

		BODYDATA,		///< 同步头结束，正文数据开始(可以开始保存帧数据了)
		RESYNCHRONIZED	///< 发生再同步，前一帧必须结束(帧长度应减去后面的再同步头长度)
	}

    // 同步头标志
	private byte[] SYNC;
	// 同步头FIFO
	private byte[] fifo;
	// FIFO中数据长度
	private int fifoLen;
	// 当前同步状态(新的字节处理之前的状态)
	private SyncState currentState = SyncState.MISSSYNC;

	// 最大帧长度(不含前导码长度)
	private int maxFrameLen;
	// 最小帧长度(不含前导码长度)
	private int minFrameLen;
	// 帧间隔时间ms(超过此时间则认为当前帧已结束): =0 表示不启用
	private int frameInterval;

	// 实际帧长度(不含前导码长度)
	private int frameLen;
	protected static final int NEED_DATA = -1;
	protected static final int UNKNOWN = 0;
	protected static final int COMPLETE = Integer.MAX_VALUE;

	// 自己的多帧组帧缓冲区(使用 direct 缓冲区，以提升性能)
	private IoBufferEx buf;

	// 对应的 session
	private IoSession session;
	// 对应的 解码输出队列
	private ProtocolDecoderOutput out;
	// 所在的 过滤器
	private ProtocolCodecFilter filter;

	/**
	 * 根据缓冲区中的数据，判断帧长度
	 * @return 帧长度: <0 表示还差多少字节; =0 表示未知；>0 表示帧长度
	 */
	protected int judgeFrameLength(IoSession session, IoBuffer in) { return UNKNOWN; }

	/**
	 * 帧解码
	 * case1: 当前帧所需要的帧数据已经到齐，进行正常解码
	 * case2: 当前帧被强制结束，强制进行解码
	 */
	protected void decodeFrame(IoSession session, IoBuffer in, ProtocolDecoderOutput out) {}

	/**
	 * 构造函数
	 * @param syncHead 同步头
	 * @param maxFrameLen 最大帧长度
	 * @param minFrameLen 最小帧长度
	 * @param frameInterval 帧间隔时间ms(超过此时间则认为当前帧已结束): =0 表示不启用
	 */
	public StreamFrameAnalyzer(byte[] syncHead, int maxFrameLen, int minFrameLen, int frameInterval) {
		SYNC = syncHead;
		fifo = new byte[SYNC.length];
		fifoLen = 0;
		currentState = SyncState.MISSSYNC;

		this.maxFrameLen = maxFrameLen;
		this.minFrameLen = minFrameLen;
		this.frameInterval = frameInterval;

		frameLen = NEED_DATA;
		buf = new IoBufferEx(IoBuffer.allocate(2*maxFrameLen, true));
	}

	/**
	 * 设置自己所在的 过滤器
	 */
	public void setFilter(ProtocolCodecFilter filter) {
		this.filter = filter;
	}

	/**
	 * 关闭并释放所有资源
	 */
	public void dispose() {
		synchronized(this) {
			buf.free();
			buf = null;
			session = null;
			out = null;
			filter = null;
			fifo = null;
			SYNC = null;
		}
	}

	/**
	 * 同步状态复位
	 */
	private void reset() {
		synchronized(this) {
			fifoLen = 0;
			changeState(SyncState.MISSSYNC);
			clearFrameBuffer();
		}
	}

	/**
	 * 确定帧长度的 3 种方式（按优先级顺序排列）：
	 * Case1: “由帧同步字确定的帧长度”
	 * Case2: “由协议栈判断的帧长度”
	 * Case3: “由于数据间隔超过LazyGap导致的强制组帧长度”
	 */
	public void feed(IoSession session, byte ch, ProtocolDecoderOutput out) {
		// Session 初始化
		if(this.session != session) {
			// 设置 session 参数
			IoSessionConfig sc = session.getConfig();
	        //sc.setMinReadBufferSize(2*maxFrameLen);
	        //sc.setMaxReadBufferSize(10*maxFrameLen);
	        sc.setReaderIdleTime(frameInterval/1000);
	        // 挂接回调对象
			session.setAttribute("proto", this);
			this.session = session;
			this.out = out;
		}

		// 同步状态检查
		switch(sync(ch))
		{
		case SYNCHRONIZED:
			// 已同步，清空帧缓冲区，准备接收数据
			clearFrameBuffer();
			return;
		case BODYDATA:
			// 在外面继续处理
			break;
		case RESYNCHRONIZED:
			// 发生再同步，前一帧必须强制结束(帧长度应减去后面的再同步头长度)
			// -1 是因为当前字节还没有放进帧缓冲区
			buf.unput(SYNC.length - 1);

			if(buf.unreading() >= minFrameLen) {
				// 强制结束当前帧，并尝试进行解码
				//LOG.debug("发生再同步，强制解码，长度: {}", buf.unreading());
				doDecodeFrame();
			}

			clearFrameBuffer();
			return;
		default:
			// 未同步，什么也不干，直接返回
			return;
		}

		try {
			// 是帧的正文数据，放入帧缓冲区
			buf.setForWrite().put(ch);
		} catch (Exception e) {
			LOG.error("帧缓冲区满，状态复位！");
			reset();
			return;
		}

		// 检查数据是否已经到齐
		if(checkFrameLength() == COMPLETE) {
			// 数据到齐，开始正常解码
			//LOG.debug("数据到齐，正常解码，长度: {}", buf.unreading());
			doDecodeFrame();
			// 状态复位，准备接收新的帧数据
			reset();
		}
	}

	/**
	 * 检查帧同步
	 * 如果 old == SYNC_SYNCHRONIZED，而当前字节导致 SYNC_MISSSYNC，则表示 SYNC_BODYSTART
	 * 如果在 SYNC_BODYSTART 状态，发生 SYNC_SYNCHRONIZED，则表示 SYNC_RESYNCHRONIZED
	 * @param ch 数据
	 * @return 同步字长度: >0 表示已同步; <=0 表示未同步;
	 */
	private SyncState sync(byte ch) {
		fifoAppend(ch);
		int len = checkSyncLength();
		SyncState newState;

		if(len == SYNC.length) {
			switch(currentState) {
			case BODYDATA:
				newState = SyncState.RESYNCHRONIZED; break;
			case MISSSYNC:
			case PENDING:
			case SYNCHRONIZED:
			case RESYNCHRONIZED:
			default:
				newState = SyncState.SYNCHRONIZED; break;
			}
		} else if(len == 0) {
			switch(currentState) {
			case SYNCHRONIZED:
			case RESYNCHRONIZED:
				newState = SyncState.BODYDATA; break;
			case BODYDATA:
				newState = SyncState.BODYDATA; break;
			case MISSSYNC:
			case PENDING:
			default:
				newState = SyncState.MISSSYNC; break;
			}
		} else {
			switch(currentState) {
			case MISSSYNC:
			case PENDING:
				newState = SyncState.PENDING; break;
			case SYNCHRONIZED:
			case RESYNCHRONIZED:
			case BODYDATA:
			default:
				newState = SyncState.BODYDATA; break;
			}
		}

		changeState(newState);
		return newState;
	}

	/**
	 * 帧间隔超时，强制结束当前帧，并尝试进行解码
	 */
	public void onFrameIntervalTimeout() {
		int len = buf.unreading();

		// 如果没有数据，则直接返回
		if(len == 0) { return; }

		// 如果数据长度不足，则复位(抛弃当前数据)
		if(len < minFrameLen) {
			reset();
			return;
		}

		//LOG.debug("超时强制解码，长度: {}", len);
		doDecodeFrame();
		// 状态复位，准备接收新的帧数据
		reset();
	}

	/**
	 * 调用派生类解码接口解码，并 flush 输出
	 */
	private void doDecodeFrame() {
		buf.setForRead();
		decodeFrame(session, buf, out);
		out.flush(session.getFilterChain().getNextFilter(filter), session);
	}

	/**
	 * 清除帧数据缓冲区
	 */
	private void clearFrameBuffer() {
		int len = buf.unreading();
		if(len > 0) {
			LOGCOMM.debug("丢弃 {} 字节数据.", len);
		}
		frameLen = NEED_DATA;
		buf.clear();
	}

	/**
	 * 改变当前状态
	 * @return 改变前的状态
	 */
	private SyncState changeState(SyncState newState) {
		SyncState old = currentState;
		currentState = newState;
		//LOG.debug("SYNC: {} -> {}", old.toString(), newState.toString());
		return old;
	}

	/**
	 * 检查缓冲区中数据长度情况
	 * @return 数据长度情况:
	 * 		NEED_DATA(-1): 数据长度不足以判断帧长度
	 * 		COMPLETE(MAXINT): 数据已经到齐
	 */
	private int checkFrameLength() {
		// 当前缓冲区中可读的数据长度
		int len = buf.unreading();

		if((frameLen == NEED_DATA) && (len >= minFrameLen)) {
			buf.setForRead();
			frameLen = judgeFrameLength(session, buf);
			buf.rewind();
		}

		if(frameLen == UNKNOWN) {
			if(len >= maxFrameLen) {
				return COMPLETE;
			} else {
				return NEED_DATA;
			}
		}

		if(frameLen > 0) {
			if((len >= frameLen) || (len >= maxFrameLen)) {
				return COMPLETE;
			}
		}

		return NEED_DATA;
	}

	// 同步头添加新字节数据
	private void fifoAppend(byte ch) {
		if(fifoLen < SYNC.length) {
			// FIFO 还没满，直接添加到后面
			fifo[fifoLen] = ch; fifoLen++;
		} else {
			// FIFO 已经满了，向左移，在最后面添加
			int i;
			for(i=0; i<(SYNC.length-1); i++) {
				fifo[i] = fifo[i+1];
			}
			fifo[i] = ch;
		}
	}

	// 检查同步头匹配长度
	private int checkSyncLength() {
		int i;
		for(i=0; i<fifoLen; i++) {
			if(fifo[i] != SYNC[i]) {
				break;
			}
		}
		return i;
	}
}
